//#########################################################################
//                              WackiEngie
//           Copyright (c) 2014 Marc Wacker. All rights reserved.
//#########################################################################

#include "WeUtilPrerequisites.h"

#pragma once

namespace WackiEngine
{



	// Common data for all event connections
	class BaseConnectionData
	{
	public:
		bool isValid;
	};

	// Event handle. Allows you to keep track of events you subscribed to and disconnect from them
	class EventHandle
	{
	public:
		EventHandle()
			: _disconnectCallback(nullptr), _connection(nullptr), _event(nullptr)
		{ }
		
		EventHandle(std::shared_ptr<BaseConnectionData> connection, void* event, void(*disconnectCallback) (const std::shared_ptr<BaseConnectionData>&, void*))
			: _disconnectCallback(disconnectCallback), _connection(connection), _event(event)
		{ }

		// disconnect from the event you are subscribed to
		void disconnect()
		{
			if(_connection != nullptr && _connection->isValid)
				_disconnectCallback(_connection, _event);
		}

	private:
		void(*_disconnectCallback) (const std::shared_ptr<BaseConnectionData>&, void*);
		std::shared_ptr<BaseConnectionData> _connection;
		void* _event;
	};
	
	/*
	
	TODO:
		1. can we provide an own disconnect functionality?
			- check why the evenhandle is needed, I don't really see its usefullness tbh...
		2. check if you can slim down the class some more
		3. make elaborate tests,
			- global functions
			- member functions
			- more?
	
	
	*/

	// allow for custom signature
	template<class Signature>
	class Event;


	// Events working like delegates or more precisely: signals. You can subscribe to get notified when it is triggered
	template<class RetType, class... Args>
	class Event<RetType(Args...)>
	{
		struct ConnectionData : BaseConnectionData
		{
		public: 
			ConnectionData(std::function<RetType(Args...)> func)
				: func(func)
			{ }

			std::function<RetType(Args...)> func;
		};

		struct InternalData
		{
			InternalData()
				: hasDisconnectedCallbacks(false)
			{ }

			Vector<std::shared_ptr<ConnectionData>> connections;
			bool hasDisconnectedCallbacks;
			
		};

	public:
		Event()
			: _internalData(new InternalData)
		{ }

		~Event()
		{
			clear();
		}


		/** Connect a new callback function to this Event.
			
			@param	
				func	std::function object that will be called when this event is triggered. 
						Remember, a std::function object can be created by just passing in any 
						normal static function pointer.
		*/
		EventHandle connect(std::function<RetType(Args...)> func)
		{
			auto connData = std::shared_ptr<ConnectionData>(new ConnectionData(func));
			
			_internalData->connections.push_back(connData);

			return EventHandle(connData, this, &Event::disconnectCallback);
		}

		/** Connect a new instance callback function to this Event.

			@param	
				c		Pointer to the class instance that contains the callback function.
			@param
				func	Pointer to the member function.
		*/
		template<class C>
		EventHandle connect(C* c, RetType(C::*func)(Args...))
		{
			// lambdas are awesome
			return connect([c, func](Args&&... args) {
				return (c->*func)(std::forward<Args>(args)...);
			});
		}

		// notify the registered callbacks for this event
		void operator() (Args... args)
		{
			// increase ref count in case a callback delets this event
			std::shared_ptr<InternalData> internalData = _internalData;

			// remove callbacks that have been disconnected
			if(_internalData->hasDisconnectedCallbacks)
			{
				for(uint32 i = 0; i < _internalData->connections.size(); ++i)
				{
					if(!_internalData->connections[i]->isValid)
					{
						_internalData->connections.erase(_internalData->connections.begin() + i);
						i--;
					}
				}

				_internalData->hasDisconnectedCallbacks = false;
			}


			uint32 numConnections = (uint32)_internalData->connections.size();
			for(uint32 i = 0; i < numConnections; ++i)
			{
				if(_internalData->connections[i]->func != nullptr)
					_internalData->connections[i]->func(args...);
			}

		}


		// clear all the callbacks
		void clear()
		{
			for(auto& connection : _internalData->connections)
			{
				connection->isValid = false;
				connection->func = nullptr;
			}

			// only flag the connection data if we actually removed something
			if(_internalData->connections.size() > 0)
				_internalData->hasDisconnectedCallbacks = true;
		}

		// are there any registered callbacks
		bool empty()
		{
			return _internalData->connections.size() == 0;
		}

	private:
		std::shared_ptr<InternalData> _internalData;

		// callback triggered by event handles when they want to disconnect from an event
		static void disconnectCallback(const std::shared_ptr<BaseConnectionData>& connection, void* event)
		{
			Event<RetType(Args...)>* castEvent = reinterpret_cast<Event<RetType(Args...)>*>(event);

			castEvent->disconnect(connection);
		}

		// internal function that disconnects the callback
		void disconnect(const std::shared_ptr<BaseConnectionData>& connData)
		{
			std::shared_ptr<ConnectionData> castConnData = std::static_pointer_cast<ConnectionData>(connData);

			for(auto& connection : _internalData->connections)
			{
				if(connection == castConnData)
				{
					castConnData->isValid = false;
					castConnData->func = nullptr;
					_internalData->hasDisconnectedCallbacks = true;
					return;
				}
			}

		}

	};
	
}